package com.spring;

import com.spring.utils.ScopeType;
import com.spring.utils.Validate;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;

import static com.spring.utils.ScopeType.*;
import static com.spring.utils.Validate.*;

public class ZhaoWFApplicationContext {
    private final ClassLoader classLoader = ZhaoWFApplicationContext.class.getClassLoader();
    private final List<String> classNames = new ArrayList<>();
    private final Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
    private final ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();

    public ZhaoWFApplicationContext(Class<?> configClass) {
        // 解析配置类
        // ComponentScan注解--->扫描路径--->扫描
        scanConfigClass(configClass);
        beanDefinitionMap.forEach(this::createSingletonBean);
    }

    private void createSingletonBean(String beanName, BeanDefinition beanDefinition) {
        if (beanDefinition.scope().equals(SINGLETON)) {
            singletonObjects.put(beanName, createBean(beanDefinition));
        }
    }

    private void scanConfigClass(Class<?> configClass) {
        String path = configClass.getDeclaredAnnotation(ComponentScan.class).value();  //扫描路径
        URL resource = classLoader.getResource(path.replace(".", "/"));
        assert resource != null;
        getAllClassNames(new File(resource.getFile()), removeSuffix(path));

        classNames.stream().map(this::getaClass).filter(clazz -> clazz.isAnnotationPresent(Component.class)).forEach(this::getBeanDefinition);
    }

    private Class<?> getaClass(String className) {
        try {
            return classLoader.loadClass(className);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    private void getBeanDefinition(Class<?> clazz) {
        beanDefinitionMap.put(getBeanName(clazz), new BeanDefinition(clazz, getScopeType(clazz)));
    }

    private String getBeanName(Class<?> clazz) {
        String beanName = clazz.getDeclaredAnnotation(Component.class).value();
        return beanName.isEmpty() ? getDefaultBeanName(clazz.getName()) : beanName;
    }

    private String getDefaultBeanName(String clazzName) {
        return toLowerCaseFirstChar(clazzName.substring(clazzName.lastIndexOf(".") + 1));
    }

    private ScopeType getScopeType(Class<?> clazz) {
        return clazz.isAnnotationPresent(Scope.class) ? ScopeTypeMap.get(clazz.getDeclaredAnnotation(Scope.class).value()) : SINGLETON;
    }

    private void getAllClassNames(File file, String path) {
        if (file.isDirectory()) {
            Arrays.stream(Objects.requireNonNull(file.listFiles())).forEach(f -> getAllClassNames(f, path + "." + file.getName()));
        } else {
            Stream.of(file).map(File::getName).filter(Validate::isClassFile).forEach(fileName -> classNames.add(path + "." + removeSuffix(fileName)));
        }
    }

    public Object getBean(String beanName) {
        if (beanDefinitionMap.containsKey(beanName)) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            return beanDefinition.scope().equals(SINGLETON) ? singletonObjects.get(beanName) : createBean(beanDefinition);
        } else {
            // 不存在对应的bean
            throw new RuntimeException();
        }
    }

    public Object createBean(BeanDefinition beanDefinition) {
        Class clazz = beanDefinition.clazz();
        try {
            Object instance = clazz.getDeclaredConstructor().newInstance();

            //属性注入
            for (Field declaredField : clazz.getDeclaredFields()) {
                if (declaredField.isAnnotationPresent(Autowired.class)) {
                    Object bean = getBean(declaredField.getName());
                    declaredField.setAccessible(true);
                    declaredField.set(instance, bean);
                }
            }

            if (instance instanceof BeanNameAware) {
                ((BeanNameAware)instance).setBeanName(clazz.getName());
            }


            return instance;
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }
}
